﻿using System.Numerics;
using ImGuiNET;
using OpenGL;
using StbImageSharp;
using static SDL2.Bindings.SDL;

namespace SDL2_OpenGL_ImGui
{
    public static class Program
    {
	    private const string VShaderSrc = @"
											#version 330 core

											layout (location = 0) in vec3 inWinPos;
											layout (location = 1) in vec2 inTexPos;
											out vec4 gl_Position;
											out vec2 texPos;

											uniform mat4 modelMatrix;
											uniform mat4 viewMatrix;
											uniform mat4 projMatrix;

											void main()
											{
												gl_Position = projMatrix * viewMatrix * modelMatrix * vec4(inWinPos.x, inWinPos.y, inWinPos.z, 1.0f);
												texPos = inTexPos;
											}";

	    private const string FShaderSrc = @"
											#version 330 core

											out vec4 FragColor;
											uniform sampler2D tex;
											uniform float d;
											in vec2 texPos;

											void main()
											{
												FragColor = texture(tex, texPos);
											}";

	    private const int WindowWidth = 1280;
	    private const int WindowHeight = 720;
        public static int Main(string[] args)
        {	
	        if (SDL_Init(SDL_INIT_EVENTS) < 0)
		        throw new Exception($"SDL could not initialize! SDL_Error: {SDL_GetError()}");
	        SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 3);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DEPTH_SIZE, 24);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_STENCIL_SIZE, 8);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE);
            SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
            var window = SDL_CreateWindow("SDL2 Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WindowWidth,
                WindowHeight, SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_SHOWN);
            
            Gl.Initialize();
            var glCtx = SDL_GL_CreateContext(window);
            SDL_GL_MakeCurrent(window, glCtx);
            SDL_GL_SetSwapInterval(1);
            
            unsafe
            {
	            ImGui.DebugCheckVersionAndDataLayout(
		            ImGui.GetVersion(),
		            (uint)sizeof(ImGuiIO),
		            (uint)sizeof(ImGuiStyle),
		            (uint)sizeof(Vector2),
		            (uint)sizeof(Vector4),
		            (uint)sizeof(ImDrawVert),
		            sizeof(ushort));
            }
            

            var guiCtx = ImGui.CreateContext();
            ImGui.SetCurrentContext(guiCtx);
            
            
            var io = ImGui.GetIO();
            io.ConfigFlags = io.ConfigFlags | ImGuiConfigFlags.NavNoCaptureKeyboard |
                             ImGuiConfigFlags.NavEnableGamepad | ImGuiConfigFlags.DockingEnable;
            
            ImGui.StyleColorsDark();
            
            ImGuiImplSdl2OpenGl.Init(window, glCtx);
            
            io.Fonts.AddFontFromFileTTF("font/SourceHanSansCN-Regular.ttf", 12, IntPtr.Zero,
	            io.Fonts.GetGlyphRangesChineseSimplifiedCommon());
            
            var shader = new Shader("TestShader", VShaderSrc, FShaderSrc);
            if (!shader.Build())
	            Console.WriteLine(shader.ErrInfo);

            var vertices = new [] {
	            0.5f,  0.5f, 0.5f,  1.0f, 1.0f,   // 右上
	            0.5f, -0.5f, 0.5f,  1.0f, 0.0f,   // 右下
	            -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,   // 左下
	            -0.5f,  0.5f, 0.5f, 0.0f, 1.0f,   // 左上

	            0.5f,  0.5f, -0.5f,  1.0f, 1.0f,   // 右上
	            0.5f, -0.5f, -0.5f,  1.0f, 0.0f,   // 右下
	            -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,   // 左下
	            -0.5f,  0.5f, -0.5f, 0.0f, 1.0f,   // 左上

	            0.5f,  0.5f, -0.5f,  1.0f, 1.0f,   // 右上
	            0.5f,  0.5f, 0.5f,  1.0f, 0.0f,   // 右下
	            -0.5f, 0.5f, 0.5f, 0.0f, 0.0f,   // 左下
	            -0.5f, 0.5f, -0.5f, 0.0f, 1.0f,    // 左上

	            0.5f,  -0.5f, -0.5f,  1.0f, 1.0f,   // 右上
	            0.5f,  -0.5f, 0.5f,  1.0f, 0.0f,   // 右下
	            -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,   // 左下
	            -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,    // 左上

	            -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,   // 右上
	            -0.5f,  0.5f, 0.5f,  1.0f, 0.0f,   // 右下
	            -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,   // 左下
	            -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,    // 左上

	            0.5f,  0.5f, -0.5f,  1.0f, 1.0f,   // 右上
	            0.5f,  0.5f, 0.5f,  1.0f, 0.0f,   // 右下
	            0.5f, -0.5f, 0.5f, 0.0f, 0.0f,   // 左下
	            0.5f, -0.5f, -0.5f, 0.0f, 1.0f    // 左上
            };
            
            var indices = new uint[] {
	            0, 1, 3, // 第一个三角形
	            1, 2, 3, // 第二个三角形

	            4, 5, 7, // 第一个三角形
	            5, 6, 7,  // 第二个三角形

	            8, 9, 11, // 第一个三角形
	            9, 10, 11,  // 第二个三角形

	            12, 13, 15, // 第一个三角形
	            13, 14, 15,  // 第二个三角形

	            16, 17, 19, // 第一个三角形
	            17, 18, 19,  // 第二个三角形

	            20, 21, 23, // 第一个三角形
	            21, 22, 23,  // 第二个三角形
            };
            
            var vao = Gl.GenVertexArray();
            Gl.BindVertexArray(vao);
            var vbo = Gl.GenBuffer();
            Gl.BindBuffer(BufferTarget.ArrayBuffer, vbo);
            var ebo = Gl.GenBuffer();
            Gl.BindBuffer(BufferTarget.ElementArrayBuffer, ebo);
            
            Gl.BufferData(BufferTarget.ArrayBuffer, (uint)vertices.Length * sizeof(float), vertices, BufferUsage.StaticDraw);
            Gl.BufferData(BufferTarget.ElementArrayBuffer, (uint)indices.Length * sizeof(uint), indices, BufferUsage.StaticDraw);
            Gl.VertexAttribPointer(0, 3, VertexAttribType.Float, false, 5 * sizeof(float), 0 * sizeof(float));
            Gl.VertexAttribPointer(1, 2, VertexAttribType.Float, false, 5 * sizeof(float), 3 * sizeof(float));
            Gl.EnableVertexAttribArray(0);
            Gl.EnableVertexAttribArray(1);
            Gl.BindVertexArray(0);

            var fbo = Gl.GenFramebuffer();
            Gl.BindFramebuffer(FramebufferTarget.Framebuffer, fbo);
            var worldTexBuffer = new Texture(IntPtr.Zero, WindowWidth, WindowHeight);
            Gl.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2d, worldTexBuffer.Id, 0);
            var rbo = Gl.GenRenderbuffer();
            Gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, rbo);
            Gl.RenderbufferStorage(RenderbufferTarget.Renderbuffer, InternalFormat.DepthComponent24, WindowWidth, WindowHeight);
            Gl.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, RenderbufferTarget.Renderbuffer, rbo);
            if (Gl.CheckFramebufferStatus(FramebufferTarget.Framebuffer) != FramebufferStatus.FramebufferComplete)
	            Console.WriteLine("ERROR::FRAMEBUFFER:: Framebuffer is not complete!");
            Gl.BindFramebuffer(FramebufferTarget.Framebuffer, 0);

            var getPixelFormat = (ColorComponents components) =>
            {
	            switch (components)
	            {
		            case ColorComponents.Grey:
			            return PixelFormat.Red;
		            case ColorComponents.RedGreenBlue:
			            return PixelFormat.Rgb;
		            case ColorComponents.RedGreenBlueAlpha:
			            return PixelFormat.Rgba;
		            default:
			            throw new Exception("Image format not support.");
	            }
            };
            
            var image = ImageResult.FromStream(File.Open("img/sa.png", FileMode.Open));
            var tex = new Texture(image.Data, image.Width, image.Height, getPixelFormat(image.Comp));
            
            var proj = Matrix4x4.CreateOrthographicOffCenter(-1,  1, -1, 1, 0.1f, 100f);
            // var proj = Matrix4x4.CreatePerspectiveFieldOfView((float) WindowWidth / (float) WindowHeight, Single.Pi / 4f, 0.1f, 100f);
            var view = Matrix4x4.CreateLookAt(new Vector3(0, 0, -3), new Vector3(0, 0, 0), new Vector3(0, -1, 0));

	        Gl.Enable(EnableCap.DepthTest);

            var angle = 0f;
            var speed = 1f;
            var showDemoWindow = true;
            var showAnotherWindow = false;
            var f = 0.0f;
            var counter = 0;
            var cleanColor = new Vector3(0.45f, 0.55f, 0.60f);

            var done = false;

            while (!done)
            {
                while (SDL_PollEvent(out var sdlEvent) != 0)
                {
	                ImGuiImplSdl2OpenGl.ProcessEvent(ref sdlEvent);
                    if (sdlEvent.type == SDL_EventType.SDL_QUIT)
                        done = true;
                    if (sdlEvent.type == SDL_EventType.SDL_WINDOWEVENT 
                        && sdlEvent.window.windowEvent == SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE 
                        && sdlEvent.window.windowID == SDL_GetWindowID(window))
                        done = true;
                }
                
                Gl.BindFramebuffer(FramebufferTarget.Framebuffer, fbo);
                Gl.ClearColor(0.22f, 0.33f, 0.33f, 1f);
                Gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
                angle += speed;
                while (angle > 360)
                {
	                angle -= 360;
                }
                
                var model = Matrix4x4.CreateRotationX(float.Pi / 4);
                model *= Matrix4x4.CreateRotationZ(float.Pi / 4);
                model *= Matrix4x4.CreateRotationY(angle * float.Pi / 180);
                
                shader.UseShader();
                shader.SetMat4("projMatrix", proj);
                shader.SetMat4("viewMatrix", view);
                shader.SetMat4("modelMatrix", model);
                shader.SetInt("tex", 0);
                Gl.ActiveTexture(TextureUnit.Texture0);
                tex.Bind();
                Gl.BindVertexArray(vao);
                Gl.BindBuffer(BufferTarget.ArrayBuffer, vbo);
                Gl.BindBuffer(BufferTarget.ElementArrayBuffer, ebo);
                Gl.DrawElements(PrimitiveType.Triangles, 36, DrawElementsType.UnsignedInt, IntPtr.Zero);
                Gl.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
                
                Gl.ClearColor(cleanColor.X, cleanColor.Y, cleanColor.Z, 1f);
                Gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

                ImGuiImplSdl2OpenGl.NewFrame();
                ImGui.NewFrame();
                
                
                // 1. Show the big demo window (Most of the sample code is in ImGui.ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).
                if (showDemoWindow)
                    ImGui.ShowDemoWindow(ref showDemoWindow);
          
                // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
                {
                
                    ImGui.Begin("Hello, world!");                          // Create a window called "Hello, world!" and append into it.
                
                    ImGui.Text("This is some useful text.");               // Display some text (you can use a format strings too)
                    ImGui.Checkbox("Demo Window", ref showDemoWindow);      // Edit bool storing our window open/close state
                    ImGui.Checkbox("Another Window", ref showAnotherWindow);
                
                    ImGui.SliderFloat("float", ref f, 0.0f, 1.0f);            // Edit 1 float using a slider from 0.0f to 1.0f
                    ImGui.ColorEdit3("clear color", ref cleanColor); // Edit 3 floats representing a color
                
                    if (ImGui.Button("Button"))                            // Buttons return true when clicked (most widgets return true when edited/activated)
                        counter++;
                    ImGui.SameLine();
                    ImGui.Text($"counter = {counter}");
                
                    ImGui.Text($"Application average {1000.0f / io.Framerate} ms/frame ({io.Framerate} FPS)");
                    ImGui.End();
                }
          
                // 3. Show another simple window.
                if (showAnotherWindow)
                {
                    ImGui.Begin("Another Window", ref showAnotherWindow);   // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
                    ImGui.Text("Hello from another window!");
                    if (ImGui.Button("Close Me"))
                        showAnotherWindow = false;
                    ImGui.End();
                }

                ImGui.Begin("Render View", ImGuiWindowFlags.NoResize);
                ImGui.Image((IntPtr) worldTexBuffer.Id,
	                new Vector2(600, 600), 
	                new Vector2(0, 1f), 
	                new Vector2(1f, 0));
                ImGui.End();

                ImGui.Begin("Controller");
                ImGui.SliderFloat("Speed Control", ref speed, 0, 10);
                ImGui.End();
                
                ImGui.Render();
                ImGuiImplSdl2OpenGl.Render(ImGui.GetDrawData());
                
                SDL_GL_SwapWindow(window);
                SDL_Delay(15);
            }
            ImGuiImplSdl2OpenGl.Shutdown();
            ImGui.DestroyContext(guiCtx);
            SDL_GL_DeleteContext(glCtx);
            SDL_DestroyWindow(window);
            return 0;
        }
    }
}

